package sbc.group1.lab1.mozartspaces;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.mozartspaces.capi3.AnyCoordinator;
import org.mozartspaces.capi3.CountNotMetException;
import org.mozartspaces.capi3.Selector;
import org.mozartspaces.core.Entry;
import org.mozartspaces.core.MzsCoreException;
import org.mozartspaces.core.MzsCoreRuntimeException;
import org.mozartspaces.core.RequestFuture;
import org.mozartspaces.core.TransactionReference;
import org.mozartspaces.core.MzsConstants.RequestTimeout;
import org.mozartspaces.core.util.Nothing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sbc.group1.lab1.common.Car;
import sbc.group1.lab1.common.Logistician;
import sbc.group1.lab1.common.Wheel;
import sun.misc.Signal;
import sun.misc.SignalHandler;

class LogisticianImpl implements Runnable, Logistician {
    public static final Logger log = LoggerFactory.getLogger(LogisticianImpl.class);
    
    public static void main(String[] args) throws MzsCoreException, URISyntaxException, IOException, InterruptedException {
        LogisticianImpl logistician= new LogisticianImpl(new File(args[0]).toURI().toURL(), new URI(args[1]),
                    Integer.parseInt(args[2]));
        final Thread t = new Thread(logistician, "logistician");
        Signal.handle(new Signal("INT"), 
        new SignalHandler() {
            @Override
            public void handle(Signal sig) {
                t.interrupt();
            }
        });
        if (System.getProperty("benchmark") != null && System.getProperty("benchmark").equals("true")) {
            Signal.handle(new Signal("HUP"), 
                    new SignalHandler() {
                        @Override
                        public void handle(Signal sig) {
                            t.start();
                        }
                    });
        }else {
            t.start();
            System.in.read();
            t.interrupt();
            t.join();
        }
    }

    private SpaceClient space;
    private volatile boolean running;
    private final int workerId;
    private final List<Selector> selectors = new LinkedList<Selector>();
    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    
    public LogisticianImpl(URL configFile, URI spaceURI, int workerId) throws MzsCoreException {
        space= new SpaceClient();
        space.init(configFile, spaceURI);
        this.workerId=workerId;

        selectors.add(CarCoordinator.newSelector());
    }
    
    @Override
    public int getId() {
        return workerId;
    }
    @Override
    public void run() {
        log.info("Starting");
        running=true;
        try {
            while(running) {
                try {
                    markCar();
                    log.debug("Marking car as finished succeeded.");
                } catch (MzsCoreException e1) {
                    log.error("Marking car as finished failed!",e1);
                }catch (MzsCoreRuntimeException e) {
                    if (e.getCause() instanceof InterruptedException) {
                        log.info("Interrupted! Stopping...");
                        running=false;
                        return;
                    }else {
                        log.error("", e);
                        running=false;
                        return;
                    }
                }catch (InterruptedException e) {
                    log.info("Interrupted! Stopping...");
                    running=false;
                    return;
                }
            }
        }finally {
            executor.shutdownNow();
        	space.getCore().shutdown(true);
        }
    }
    
    private void markCar() throws MzsCoreException, InterruptedException {

        TransactionReference transaction = space.getCapi().createTransaction(
                RequestTimeout.INFINITE, space.getSpaceUri());
        try {
            Car car = space.getCapi().<Car>take(
                    space.getTestedCarContainer(), 
                    selectors, RequestTimeout.INFINITE,transaction).get(0);
            if (car.isMarkedAsDefect()) {
                treatDefectCar(car, transaction);
            }else {
                treatOkCar(car, transaction);
            }
            transaction=null;
        }finally {
        	try {
	        	if (transaction!=null) {
	        		space.getCapi().rollbackTransaction(transaction);
	        	}
        	}catch(Exception e) {
        	}
        }
    }
    
    private void treatDefectCar(Car car, TransactionReference transaction) throws MzsCoreException {
        log.debug("Treating defect car.");
        space.getCapi().write(space.getDefectCarContainer(), RequestTimeout.INFINITE, 
                transaction, new Entry(car));
        LinkedList<Entry> wheels = new LinkedList<Entry>();
        if (car.getWheels()!=null) {
            for (Wheel wheel : car.getWheels()) {
                if (!wheel.isDefect()) {
                    wheels.add(new Entry(wheel));
                }
            }
            if (wheels.size()!=0) {
                space.getCapi().write(wheels, space.getWheelContainer(), RequestTimeout.INFINITE,
                        transaction);
            }
        }
        if (car.getEngine()!=null) {
            if (!car.getEngine().isDefect()) {
                space.getCapi().write(space.getEngineContainer(), RequestTimeout.INFINITE,
                        transaction, new Entry(car.getEngine()));
            }
        }
        if (car.getCarBody()!=null) {
            if (!car.getCarBody().isDefect()) {
                space.getCapi().write(space.getCarBodyContainer(), RequestTimeout.INFINITE,
                        transaction, new Entry(car.getCarBody()));
            }
        }
        if (car.getOrder()!=null) {
            space.getCapi().write(space.getCoatingJobContainer(car.getOrder()), RequestTimeout.INFINITE,
                    transaction, new Entry(new CoatingJob(car.getOrder())));
            space.getCapi().write(space.getAssemblyJobContainer(car.getOrder()), RequestTimeout.INFINITE,
                    transaction, new Entry(new AssemblyJob(car.getOrder().getColor(), car.getOrder().getEngine())));
            try {
                space.getCapi().delete(space.getOrderJobsReissueContainer(),
                       AnyCoordinator.newSelector(),RequestTimeout.TRY_ONCE,transaction);
            }catch (CountNotMetException e) {
            }
            space.getCapi().write(space.getOrderJobsReissueContainer(), RequestTimeout.INFINITE,
                    transaction, new Entry(car.getOrder()));
        }
        executor.execute(new FailedTransactionHandler( 
                null,
                space.getAcapi().commitTransaction(transaction),
                transaction, space.getCapi()));
    }
    
    private void treatOkCar(Car car, TransactionReference transaction) throws MzsCoreException {
        car.markAsFinished(this);
        if (car.getOrder()!=null) {
            try {
            space.getCapi().delete(space.getCarsLeftContainer(car.getOrder()), 
                    AnyCoordinator.newSelector(1), 
                    RequestTimeout.TRY_ONCE,
                    transaction);
            }catch(CountNotMetException e) {
                space.finishOrder(car.getOrder(),transaction);
            }
        }
        RequestFuture<Nothing> writeOp = space.getAcapi().write(
                space.getFinishedCarContainer(), RequestTimeout.INFINITE, 
                transaction, new Entry(car));
        executor.execute(new FailedTransactionHandler(writeOp,
                space.getAcapi().commitTransaction(transaction),
                transaction, space.getCapi()));
    }
}
